#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <mutex>
#include "../comm/Log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"
#include <unistd.h>
#include "oj_model.hpp"
#include "oj_view.hpp"
#include <jsoncpp/json/json.h>



namespace controlNs {
    using namespace utilNs;
    using namespace logNs;
    using namespace modelNs;
    using namespace viewNs;
    using namespace httplib;
    const std::string machines_path = "./machines.conf";

    // 描述主机的结构体
    class Machine {
        public :
        Machine() 
            :_ip("")
            ,_port(0)
            ,_load(0)
            ,_mtx(nullptr)
        {

        }
        ~Machine() {
        }
        std::string _ip;
        uint16_t _port;
        int _load;
        std::mutex* _mtx;
        int Load() {
            int load = 0;
            if (_mtx) {
                _mtx->lock();
            }
            load = _load;
            if (_mtx) {
                _mtx->unlock();
            }
            return load;
        } 

        void IncLoad() {
            if (_mtx) {
                _mtx->lock();
            }
            _load++;
            if (_mtx) {
                _mtx->unlock();
            }
        }

        void ClearLoad() {
            if (_mtx) {
                _mtx->lock();
            }
            _load = 0;
            if (_mtx) {
                _mtx->unlock();
            }
        }
        void DecLoad() {
            if (_mtx) {
                _mtx->lock();
            }
            _load--;
            if (_mtx) {
                _mtx->unlock();
            }
        }
    };

    // 负载均衡模块
    class LoadBlance {
    public :
        LoadBlance() {
            assert(LoadConf());
        }

        bool LoadConf() {
            std::ifstream in(machines_path);
            if (!in.is_open()) {
                LOG(Fatal) << "加载主机失败, 请检查是否存在主机配置文件" << std::endl;
                return false;
            }
            std::string line;
            while (std::getline(in, line)) {
                std::vector<std::string> tokens;
                StringUtil::splitString(line, &tokens, ":");
                if (tokens.size() != 2) {
                    LOG(Waring) << "加载单个主机出错, 请检查主机配置文件的格式" << std::endl;
                    continue;
                }
                Machine m;
                m._mtx = new std::mutex();
                m._ip = tokens[0];
                m._port = std::stoi(tokens[1]);
                _online_machines.push_back(_machines.size()); // 刚刚加载到的主机默认是在线的
                _machines.push_back(m);
            }
            in.close();
            return true;
        }

        // 负载均衡地选择主机
        bool SmartChoice(int *id, Machine** m) {
            if (_online_machines.empty()) {
                LOG(Fatal) << "所有的主机已经离线, 请运维的同事尽快处理" << std::endl;
                return false;
            }
            _mtx.lock();
            *id = _online_machines[0];
            *m = &_machines[*id];
            int min_load_id = _online_machines[0];
            int min_load = _machines[min_load_id].Load();
            for (auto _id : _online_machines) {
                int cur_load = _machines[_id].Load();
                if (cur_load < min_load) {
                    min_load = cur_load;
                    *id = _id;
                    *m = &_machines[_id];
                }
            }
            _mtx.unlock();
            return true;
        }

        void OfflineMachine(int id) {
            for (auto iter = _online_machines.begin(); iter != _online_machines.end(); iter++) {
                if (*iter == id) {
                    // 要被离线的主机找到了
                    _online_machines.erase(iter);
                    _offline_machines.push_back(id);
                    _machines[id].ClearLoad(); // 将离线的主机负载清零
                    LOG(Debug) << "主机:[" << _machines[id]._ip << "][" << _machines[id]._port << "]可能已经离线, 请运维同事尽快查看" << std::endl; 
                    break;
                }
            }
        }

        // 统一上线所有主机
        void OlineMachie() {
            for (auto id : _offline_machines) {
                _online_machines.push_back(id);
            }
            _offline_machines.clear();
            LOG(Info) << "所有的主机已经全部上线啦" << std::endl;
        }


    private :
        std::vector<Machine> _machines;
        std::vector<int> _online_machines;
        std::vector<int> _offline_machines;
        std::mutex _mtx;
    };

    // 项目的核心业务控制模块——control
    class Control {
    public :
        // 获取一张包含所有题目列表的网页
        void GetAllQuestionsHtml(std::string* out_html) {
            std::vector<Question> all;
            _model.GetAllQuestions(&all);
            _view.ExpandAllHtml(all, out_html);
        }

        void GetOneQuestionHtml(const int number, std::string* out_html) {
            Question q;
            _model.GetOneQuestion(number, &q);
            _view.ExpandOneHtml(q, out_html);
        }


        // 判定指定编号的一道题
        void Judge(const std::string& number, const std::string& in_json, std::string* out_json) {
            // 1、根据题目编号，获得对应题目的细节，内容
            Question q;
            _model.GetOneQuestion(std::stoi(number), &q);
            std::string question_desc = q._desc;
            std::string question_header = q._header;
            std::string question_tail = q._tail;
            int question_cpu_limit = q._cpu_limit;
            int question_mem_limit = q._mem_limit;

            // 对in_json进行反序列化，得到用户提交的源代码和input信息
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);
            std::string code_content = in_value["code"].asString();
            std::string input_content = in_value["input"].asString();

            // 选择负载最低的主机
            while (true) {
                int id = 0;
                Machine* m = new Machine();
                if (_load_blance.SmartChoice(&id, &m)) {
                    // 构建请求json串
                    Json::Value complie_json;
                    Json::FastWriter writer;
                    complie_json["code"] = code_content + "\n" + question_tail;
                    complie_json["input"] = input_content;
                    complie_json["cpu_limit"] = question_cpu_limit;
                    complie_json["mem_limit"] = question_mem_limit;
                    // 序列化
                    std::string complie_string = writer.write(complie_json);
                    // 创建客户端，发起http请求
                    Client cli(m->_ip, m->_port);
                    m->IncLoad(); // 有请求到来，负载增加
                    if (auto res = cli.Post("/complie_and_run", complie_string, "application/json;charset=utf-8")) {
                        // 将结果赋值给out_json
                        if (res->status == 200) {
                            LOG(Info) << "成功选择主机:[" << m->_ip << "][" << m->_port << "], 当前主机负载: " << m->Load() << std::endl;
                            *out_json = res->body;
                            m->DecLoad();
                            break;
                        }
                        m->DecLoad();
                    } else {
                        // 请求的主机可能已经离线
                        // 离线该主机
                        _load_blance.OfflineMachine(id);
                    }
                } else {
                    break;
                }
            }
        }

        // 上线所有主机
        void RecoveryMachine() {
            _load_blance.OlineMachie();
        }


    private :
        Model _model; // 提供获取题目信息的功能
        View _view;
        LoadBlance _load_blance;
    };
};